Skip to content

Decouple ILC from ManagedAssemblyToLink#124801

Draft
Copilot wants to merge 5 commits intomainfrom
copilot/fix-ilc-compile-order
Draft

Decouple ILC from ManagedAssemblyToLink#124801
Copilot wants to merge 5 commits intomainfrom
copilot/fix-ilc-compile-order

Conversation

Copy link
Contributor

Copilot AI commented Feb 24, 2026

_ComputeManagedAssemblyForILLink in NativeAOT was indirectly dependent on ComputeIlcCompileInputs via @(ManagedBinary), which made PrepareForILLink ordering-sensitive and broke incremental ILLink behavior when ILLink is run before ILC input computation. This change removes that coupling so ILLink preparation no longer relies on ComputeIlcCompileInputs side effects.

  • What changed

    • Updated src/coreclr/nativeaot/BuildIntegration/Microsoft.NETCore.Native.targets in _ComputeManagedAssemblyForILLink.
    • Replaced @(ManagedBinary) with @(IntermediateAssembly) when reconstructing @(ManagedAssemblyToLink).
  • Why this matters

    • @(IntermediateAssembly) is available during PrepareForILLink, independent of ComputeIlcCompileInputs.
    • This preserves inclusion of the project assembly in @(ManagedAssemblyToLink) regardless of whether ComputeIlcCompileInputs runs before or after PrepareForILLink/ILLink.
  • Code change

    <ManagedAssemblyToLink Include="@(DefaultFrameworkAssemblies);@(_ManagedResolvedAssembliesToPublish);@(IntermediateAssembly)" />
Original prompt

This section details on the original issue you should resolve

<issue_title>ComputeIlcCompileInputs should not need to run before PrepareForILLink</issue_title>
<issue_description>## Description

The default IlcCompileDependsOn in Microsoft.NETCore.Native.targets orders ComputeIlcCompileInputs before PrepareForILLink:

Compile;ComputeIlcCompileInputs;SetupOSSpecificProps;PrepareForILLink

This couples ILC's input computation to ILLink's preparation phase. _ComputeManagedAssemblyForILLink (which runs AfterTargets="_ComputeManagedAssemblyToLink" during PrepareForILLink) consumes @(ManagedBinary), a side effect of ComputeIlcCompileInputs. This ordering works for the standard pipeline because it doesn't actually run ILLink (RunILLink=false), but it breaks consumers that need PrepareForILLink and ILLink to run before ComputeIlcCompileInputs.

Why a consumer would need the opposite order

The standard NativeAOT pipeline sets RunILLink=false — ILLink never actually runs, and @(ManagedAssemblyToLink) is only used as metadata for ILC. A consumer that sets RunILLink=true to actually trim assemblies before ILC needs ILLink to complete first, so that ILC consumes the trimmed output. This requires PrepareForILLink and ILLink to precede ComputeIlcCompileInputs — the opposite of the default order. This is the case in .NET for Android's NativeAOT pipeline.

Impact

When PrepareForILLink runs before ComputeIlcCompileInputs, _ComputeManagedAssemblyForILLink builds its replacement @(ManagedAssemblyToLink) list before @(ManagedBinary) has been populated. The project assembly ends up missing from @(ManagedAssemblyToLink), which is used as Inputs by _RunILLink (in Microsoft.NET.ILLink.targets). This causes ILLink's incremental build check to miss changes to the project assembly, so ILLink skips on rebuild even though the assembly changed.

First builds still succeed because PrepareForILLink independently adds @(IntermediateAssembly) as a TrimmerRootAssembly, so ILLink loads and processes it regardless. Only incremental builds are affected.

Reproduction

Create a dotnet new console project with the following csproj (note: explicit SDK imports are needed because Microsoft.NETCore.Native.targets unconditionally sets RunILLink=false, so it must be overridden after the SDK targets load):

<Project>
  <Import Project="Sdk.props" Sdk="Microsoft.NET.Sdk" />

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net11.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <PublishAot>true</PublishAot>
    <RuntimeIdentifier>linux-x64</RuntimeIdentifier>
  </PropertyGroup>

  <Import Project="Sdk.targets" Sdk="Microsoft.NET.Sdk" />

  <PropertyGroup>
    <RunILLink>true</RunILLink>
    <IlcCompileDependsOn>
      Compile;
      SetupOSSpecificProps;
      PrepareForILLink;
      ILLink;
      ComputeIlcCompileInputs
    </IlcCompileDependsOn>
  </PropertyGroup>
</Project>

Run dotnet publish, then change "Hello, World!" to "Hello, Changed!" in Program.cs and publish again. On the second publish, ILLink is skipped (no "Optimizing assemblies for size" message) and the trimmed assembly in obj/.../linked/ReproApp.dll still contains Hello, World!.

Suggestion

Decouple ComputeIlcCompileInputs from PrepareForILLink so neither depends on having run before the other. Ideally _ComputeManagedAssemblyForILLink should not rely on state produced by ComputeIlcCompileInputs, and ComputeIlcCompileInputs should be free to run after ILLink without breaking the ILLink preparation phase.

Workaround

.NET for Android can work around this by injecting @(IntermediateAssembly) into @(ManagedAssemblyToLink) after _ComputeManagedAssemblyForILLink replaces it:

<Target Name="_AndroidFixManagedAssemblyToLink"
    AfterTargets="_ComputeManagedAssemblyForILLink">
  <ItemGroup>
    <ManagedAssemblyToLink Include="@(IntermediateAssembly)" />
  </ItemGroup>
</Target>
```</issue_description>

<agent_instructions>Decouple these targets. The ILC inputs should not be resolved from ManagedAssemblyToLink.</agent_instructions>

## Comments on the Issue (you are @copilot in this section)

<comments>
</comments>

💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more Copilot coding agent tips in the docs.

@github-actions github-actions bot added the area-Tools-ILLink .NET linker development as well as trimming analyzers label Feb 24, 2026
…puts

Co-authored-by: sbomer <787361+sbomer@users.noreply.github.com>
Copilot AI changed the title [WIP] Fix ComputeIlcCompileInputs execution order before PrepareForILLink Decouple NativeAOT ILLink assembly input computation from ComputeIlcCompileInputs Feb 24, 2026
Copilot AI requested a review from sbomer February 24, 2026 17:25
…lyToLink

Factor _PrepareTrimConfiguration out of PrepareForILLink so shared trim
configuration operates on ResolvedFileToPublish and flows to both ILLink
and ILC without duplication.

For NativeAOT, _ComputeAssembliesToCompileToNative replaces CoreCLR
runtime pack files with DefaultFrameworkAssemblies (tagged
PostprocessAssembly=true) before _PrepareTrimConfiguration runs.
_ComputeIlcCompileInputs then derives IlcReference from
ResolvedFileToPublish via PostprocessAssembly metadata, ensuring ILC
sees ILLink-relocated paths when both run.

ComputeLinkedFilesToPublish hooks AfterTargets=ILLink (instead of
ComputeResolvedFilesToPublishList) for correct ordering when both
ILLink and ILC run. RunILLink is made conditional so projects can
opt in.

Remove ManagedAssemblies output from the C# task (replaced by
ResolvedFileToPublish filtering). Add RuntimePackFilesToSkipPublish
output for early removal of CoreCLR runtime pack files. Fix OOB
assembly handling so overrides stay in ResolvedFileToPublish.
@dotnet-policy-service dotnet-policy-service bot added the linkable-framework Issues associated with delivering a linker friendly framework label Feb 25, 2026
@sbomer sbomer changed the title Decouple NativeAOT ILLink assembly input computation from ComputeIlcCompileInputs Decouple ILC from ManagedAssemblyToLink Feb 25, 2026
Skip _ComputeIlcCompileInputs for framework library builds
(BuildingFrameworkLibrary=true) since they use BuildOneFrameworkLibrary
for input computation, not the publish-pipeline targets.
When NativeCompilationDuringPublish is false (e.g. Apple non-library-mode
builds), Publish.targets is not imported so _ComputeIlcCompileInputs does
not exist to populate IlcReference from ResolvedFileToPublish. Fall back
to DefaultFrameworkAssemblies directly in ComputeIlcCompileInputs so ILC
can find System.Private.CoreLib and other framework references.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area-Tools-ILLink .NET linker development as well as trimming analyzers linkable-framework Issues associated with delivering a linker friendly framework

Projects

Status: No status

Development

Successfully merging this pull request may close these issues.

ComputeIlcCompileInputs should not need to run before PrepareForILLink

2 participants